/**
 * 
 */
package com.primestone.cim.executeenddevicecontrols.service;

import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;
import javax.sql.rowset.serial.SerialClob;

import ch.iec.tc57._2011.enddevicecontrols.EndDeviceControl;
import ch.iec.tc57._2011.enddevicecontrols.EndDeviceControls;
import ch.iec.tc57._2011.enddevicecontrolsmessage.EndDeviceControlsPayloadType;
import ch.iec.tc57._2011.schema.message.HeaderType;

import com.google.gson.Gson;
import com.primestone.cim.entities.tmp.ResEndDeviceControls;
import com.primestone.cim.executeenddevicecontrols.Completer;
import com.primestone.cim.executeenddevicecontrols.ControlOperationResult;
import com.primestone.cim.executeenddevicecontrols.ControlTypeComposer;
import com.primestone.cim.util.DateUtil;
import com.primestone.cim.util.NumberUtil;
import com.primestone.cim.util.StringUtil;
import com.primestone.cim.util.enums.RelayActionEnum;

/**
 * @author cesar.mateus
 *
 */
public class ExecEndDeviceControlsService implements Serializable, IExecEndDeviceControlsService{

	private static final long serialVersionUID = 106453855360728597L;

	private DataSource dataSource;

	private volatile static IExecEndDeviceControlsService instance = null;

	private ExecEndDeviceControlsService(){}

	private ExecEndDeviceControlsService(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public static IExecEndDeviceControlsService getInstance(DataSource dataSource) {
		if (instance == null) {
			synchronized(ExecEndDeviceControlsService.class){
				if (instance == null) {
					instance = new ExecEndDeviceControlsService(dataSource);	
				}
			}
		} 
		return instance;
	}

	@Override
	public ResEndDeviceControls getOperations(EndDeviceControlsPayloadType payload, String ticket, HeaderType header) {
		Connection conn = null;
		ResEndDeviceControls resEndDeviceControls = null;		

		try {
			conn = dataSource.getConnection();
			
			EndDeviceControls endDeviceControls = payload.getEndDeviceControls();
			
			List<EndDeviceControl> endDeviceControlList = endDeviceControls.getEndDeviceControl();
			//String mrid;
			String description;
			/**
			 * Level of a demand response program request, where 0=emergency. 
			 * Note: Attribute is not defined on DemandResponseProgram as it is not its inherent property (it serves to control it).
			 */
			//BigInteger drProgramLevel;
			/**
			 * (if applicable) Price signal used as parameter for this end device control.
			 */
			//Float priceSignal;
			String type;
//			DemandResponseProgram demandResponseProgram;
//			EndDeviceGroup endDeviceGroup;
			/**
			 * (if control has scheduled duration) Date and time interval the control has been scheduled to execute within.
			 */
//			ScheduledInterval scheduledInterval;
			List<ControlOperationResult> controlsToSave = new ArrayList<ControlOperationResult>();
			ControlOperationResult controlOperationResult;
			List<Integer> dataFromRecurrencePattern;
			for (EndDeviceControl endDeviceControl : endDeviceControlList) {
				controlOperationResult = new ControlOperationResult();
				//mrid = endDeviceControl.getMRID(); // Posible
				description = endDeviceControl.getDescription(); // Se usa la description como recurrencePattern. En este momento es OBLIGATORIA.
				if (StringUtil.safe(description)) {
					dataFromRecurrencePattern = Completer.getDataFromRecurrencePattern(description);
					controlOperationResult.setPeriod(dataFromRecurrencePattern.get(0));
					controlOperationResult.setFrequency(dataFromRecurrencePattern.get(1));
				}
				//drProgramLevel = endDeviceControl.getDrProgramLevel(); // No Soportado
				//priceSignal = endDeviceControl.getPriceSignal(); // No Soportado
				type = endDeviceControl.getType(); // OBLIGATORIO
				if (type.equals(ControlTypeComposer.getConnectCommandType().get(0))) {
					controlOperationResult.setRelayAction(RelayActionEnum.CLOSE.getAction());
				} else if (type.equals(ControlTypeComposer.getDisconnectCommandType().get(0))) {
					controlOperationResult.setRelayAction(RelayActionEnum.OPEN.getAction());
				}
				
				//demandResponseProgram = endDeviceControl.getDemandResponseProgram(); // No Soportado
				controlOperationResult.setIdDevice(endDeviceControl.getEndDeviceAsset().getMRID()); // OBLIGATORIO
				//endDeviceGroup = endDeviceControl.getEndDeviceGroup(); // No Soportado
				controlOperationResult.setDateTime(
						DateUtil.getJavaUtilDateFromXMLGregorianCalendar(endDeviceControl.getScheduledInterval().getStart())); // OBLIGATORIO
				//end = scheduledInterval.getEnd(); // No Soportado
				controlsToSave.add(controlOperationResult);
			}

			resEndDeviceControls = executeStoreProcedure(
					conn, controlsToSave, ticket, header.getUser().getUserID());
		} catch( SQLException e ) {
			e.printStackTrace();
			//			try {
			//				if( conn != null ) {
			//					conn.rollback();
			//				}
			//			} catch( SQLException ex ) {
			//				ex.printStackTrace();
			//			}
		} catch( Exception e ) {
			e.printStackTrace();
			//			try {
			//				if( conn != null ) {
			//					conn.rollback();
			//				}
			//			} catch( SQLException ex ) {
			//				ex.printStackTrace();
			//			}
		} finally {
			if( conn != null ) {
				try {
					conn.close();
				} catch( SQLException e ) {
					e.printStackTrace();
				}
			}
		}

		return resEndDeviceControls;
	}

	/**
	 * 
	 * @param conn
	 * @param ticket
	 * @param extractedSchedules
	 * @param mridsEndDevices
	 * @param userId
	 * @return
	 * @throws Exception
	 */
	private ResEndDeviceControls executeStoreProcedure( 
			Connection conn, List<ControlOperationResult> controlsToSave, String ticket, String userId ) throws Exception {
		ResEndDeviceControls resEndDeviceControls = null;
		try {
			System.out.println("---- controlsToSave: "+controlsToSave);
			List<ControlOperationResult> savedUnsavedControls = saveControls(conn, controlsToSave);

			// Colocar los resultados en formato json
			Map<String, List<ControlOperationResult>> mapforJSon = new HashMap<String, List<ControlOperationResult>>();
			mapforJSon.put(ticket, savedUnsavedControls);

			Gson gson = new Gson();
			//System.out.println("---- map res:" +mapforJSon);
			String json = gson.toJson(mapforJSon);
			//System.out.println("---- json res:" +json);

			Clob jsonClob = new SerialClob(json.toCharArray());

			resEndDeviceControls = new ResEndDeviceControls(ticket, userId, StringUtil.EMPTY, new Date(), jsonClob, StringUtil.CREATE_OPERATION);			

		} catch( Exception e ) {
			e.printStackTrace();
			throw e;
		}

		return resEndDeviceControls;
	}

	/**
	 * 
	 * pDevice VARCHAR(50),
	 * pDatetime DATETIME, --YYYYMMDD HH:MI
	 * pPeriod SMALLINT, --0 none, 1 minute, 2 hour, 3 day , 4 week, 5 month
	 * pRetries SMALLINT,
	 * pRetritime SMALLINT,
	 * pFreq SMALLINT,
	 * pIsLP SMALLINT,
	 * pIsRG SMALLINT,
	 * pIsEV SMALLINT,
	 * --pIsCallSlaves SMALLINT,
	 * --pIsTimeSynch SMALLINT,
	 * Cerror VARCHAR(100) = '' output
	 * 
	 * @param conn
	 * @param startTime
	 * @param endTime
	 * @param socket
	 * @param descVar
	 * @param varType
	 * @param ticketReadings
	 * @return
	 * @throws SQLException
	 */
	private List<ControlOperationResult> saveControls (
			Connection conn, List<ControlOperationResult> controlsToSave) throws SQLException {
		CallableStatement cs = null;
		try {
			// TODO Usar Batch Updates?: http://docs.oracle.com/javase/1.3/docs/guide/jdbc/getstart/callablestatement.html
			// TODO Hacer conn.setAutoCommit( false ); y luego de la iteracion hacer conn.commit(); ?
			String callStatement = "{ ? = call spInsertCallSchedule(?,?,?,?,?,?,?,?,?,?,?) }";
			cs = conn.prepareCall(callStatement);
			for (ControlOperationResult result : controlsToSave) {
				try {
					cs.setEscapeProcessing(true);
					//cs.setQueryTimeout(1800);
					cs.registerOutParameter(1, java.sql.Types.INTEGER);
					cs.setString(2, result.getIdDevice());
					cs.setTimestamp(3, new Timestamp(result.getDateTime().getTime()));
					cs.setInt(4, result.getPeriod());
					if (NumberUtil.safe(result.getRetries())) {
						cs.setInt(5, result.getRetries());	
					} else {
						cs.setInt(5, 0);
					}
					
					if (NumberUtil.safe(result.getRetryTime())) {
						cs.setInt(6, result.getRetryTime());	
					} else {
						cs.setInt(6, 0);
					}

					cs.setInt(7, result.getFrequency());
					cs.setInt(8, 1);
					cs.setInt(9, 1);
					cs.setInt(10, 1);
					cs.setString(11, result.getRelayAction());
					cs.setString(12, StringUtil.EMPTY);
					cs.registerOutParameter(12, java.sql.Types.VARCHAR);

					cs.executeUpdate();
					System.out.print("--- output return:" + cs.getInt(1) + " msg:" + cs.getString(12));
					result.setIsSaved(true);
					result.setMessage(StringUtil.OK);
				} catch (Exception e) {
					e.printStackTrace();
					result.setIsSaved(false);
					result.setMessage(e.getMessage());
				}
			}
		} finally {			
			if (cs != null) {
				cs.close();	
			}
		}

		return controlsToSave;
	}

	protected Object clone() {
		return instance;
	}

	private Object readResolve(){
		return instance;
	}
}
